package com.ideaaedi.commonspring.aop;

import com.ideaaedi.commonspring.lite.params.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.core.annotation.AnnotationAttributes;

import javax.annotation.Nonnull;
import java.util.Arrays;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * (non-javadoc)
 *
 * @author <font size = "20" color = "#3CAA3C"><a href="https://gitee.com/JustryDeng">JustryDeng</a></font> <img
 * src="https://gitee.com/JustryDeng/shared-files/raw/master/JustryDeng/avatar.jpg" />
 * @since 1.0.0
 */
@Slf4j
public class ParameterRecorderAdviceConfig {
    
    public static final String BEAN_NAME = "parameterRecorderAdviceConfig";
 
    private final AnnotationAttributes annotationAttributes;
    
    public ParameterRecorderAdviceConfig(AnnotationAttributes annotationAttributes) {
        this.annotationAttributes = Objects.requireNonNull(annotationAttributes, "annotationAttributes should not be null.");
    }
    
    @Bean(name = ParameterRecorderAdvice.BEAN_NAME)
    public PointcutAdvisorWithInitial parameterRecorderAdvice(ApplicationContext applicationContext,
            @Autowired(required = false) ParameterRecorderCustomizer parameterRecorderCustomizer) {
        // 参数信息
        Set<String> includePrefixes = Arrays.stream((String[])annotationAttributes.get("includePrefixes")).collect(Collectors.toSet());
        Set<String> excludePrefixes = Arrays.stream((String[])annotationAttributes.get("excludePrefixes")).collect(Collectors.toSet());
        Set<String> pointcutAnds = Arrays.stream((String[])annotationAttributes.get("pointcutAnd")).collect(Collectors.toSet());
        Set<String> pointcutOrs = Arrays.stream((String[])annotationAttributes.get("pointcutOr")).collect(Collectors.toSet());
        ParameterHandleModeEnum parameterHandleMode = (ParameterHandleModeEnum)annotationAttributes.get("parameterHandleMode");
        boolean pretty = (boolean)annotationAttributes.get("pretty");
        //noinspection rawtypes
        Class[] ignoreParamTypes = (Class[])annotationAttributes.get("ignoreParamTypes");
        String pointcut = buildPointcut(pointcutAnds, pointcutOrs);
        
        log.info("registry bean '{}' with ConstructorArg includePrefixes -> {}, excludePrefixes -> {}, "
                        + "parameterHandleMode -> {}, pretty -> {}, ignoreParamTypes -> {}, pointcut -> {}",
                ParameterRecorderAdvice.BEAN_NAME, includePrefixes, excludePrefixes,
                parameterHandleMode, pretty, ignoreParamTypes, pointcut);
        parameterRecorderCustomizer = createCustomizerIfNecessary(applicationContext, parameterRecorderCustomizer);
    
        // 切面
        AspectJExpressionPointcut aspectJExpressionPointcut = new AspectJExpressionPointcut ();
        aspectJExpressionPointcut.setExpression(pointcut);
    
        // 切面逻辑
        ParameterRecorderAdvice parameterRecorderAdvice = new ParameterRecorderAdvice(
                includePrefixes,
                excludePrefixes,
                parameterHandleMode,
                pretty,
                ignoreParamTypes,
                parameterRecorderCustomizer);
        
        // 增强器
        PointcutAdvisorWithInitial defaultPointcutAdvisor = new PointcutAdvisorWithInitial();
        defaultPointcutAdvisor.setPointcut(aspectJExpressionPointcut);
        defaultPointcutAdvisor.setAdvice(parameterRecorderAdvice);
        defaultPointcutAdvisor.setOrder(parameterRecorderAdvice.getOrder());
        return defaultPointcutAdvisor;
    }
    
    /**
     * 构建定制化器
     */
    @Nonnull
    private static ParameterRecorderCustomizer createCustomizerIfNecessary(ApplicationContext applicationContext,
                                                                           ParameterRecorderCustomizer parameterRecorderCustomizer) {
        if (parameterRecorderCustomizer != null) {
            return parameterRecorderCustomizer;
        }
        ParameterSerializer parameterSerializer =
                applicationContext.getBeanProvider(ParameterSerializer.class).getIfAvailable();
        RequestEntranceJudger requestEntranceJudger =
                applicationContext.getBeanProvider(RequestEntranceJudger.class).getIfAvailable();
        RequestPathProvider requestPathProvider =
                applicationContext.getBeanProvider(RequestPathProvider.class).getIfAvailable();
        ParameterNameDiscoverer parameterNameDiscoverer =
                applicationContext.getBeanProvider(ParameterNameDiscoverer.class).getIfAvailable();
        return new ParameterRecorderCustomizer() {
            @Override
            public ParameterSerializer customParameterSerializer() {
                if (parameterSerializer != null) {
                    return parameterSerializer;
                }
                return ParameterRecorderCustomizer.super.customParameterSerializer();
            }
        
            @Override
            @Nonnull
            public RequestEntranceJudger customRequestEntranceJudger() {
                return Objects.requireNonNullElseGet(requestEntranceJudger,
                        ParameterRecorderCustomizer.super::customRequestEntranceJudger);
            }
        
            @Override
            public RequestPathProvider customRequestPathProvider() {
                if (requestPathProvider != null) {
                    return requestPathProvider;
                }
                return ParameterRecorderCustomizer.super.customRequestPathProvider();
            }
        
            @Override
            public ParameterNameDiscoverer customParameterNameDiscoverer() {
                if (parameterNameDiscoverer != null) {
                    return parameterNameDiscoverer;
                }
                return ParameterRecorderCustomizer.super.customParameterNameDiscoverer();
            }
        };
    }
    
    /**
     * 构建切点
     */
    protected String buildPointcut(Set<String> pointcutAnds, Set<String> pointcutOrs) {
        final String andConnector = " && ";
        final String orConnector = " || ";
        String and = pointcutAnds.stream().filter(StringUtils::isNotBlank)
                .collect(Collectors.joining(andConnector));
    
        String or = pointcutOrs.stream().filter(StringUtils::isNotBlank)
                .collect(Collectors.joining(orConnector));
        if (StringUtils.isBlank(and)) {
            and = "";
        } else {
            and = andConnector + and;
        }
        if (StringUtils.isBlank(or)) {
            or = "";
        } else {
            or = orConnector + or;
        }
        return String.format(ParameterRecorder.POINTCUT_FORMAT, or, and);
    }
}
